package javango.forms.fields;

import java.lang.annotation.Annotation;
import java.util.LinkedHashMap;
import java.util.Map;

import javango.forms.ValidationException;
import javango.forms.fields.annotations.FieldProperties;
import javango.forms.widgets.TextInputWidget;
import javango.forms.widgets.Widget;
import javango.forms.widgets.WidgetFactory;

import org.apache.commons.lang.StringUtils;

import com.google.inject.Inject;

public abstract class AbstractField<T> implements Field<T> {

	public final static String REQUIRED_ERROR = "This field is required.";

	protected Object initial;
	protected String name;
	protected String label;
	protected String helpText;
	
	protected WidgetFactory widgetFactory;
	protected Class<? extends Widget> widgetClass; 
	protected Widget widget;
	protected Class<? extends Widget> hiddenWidgetClass;
	protected Widget hiddenWidget;
	Map<String, String> widgetAttrs;

	private boolean required = true;
	private boolean hidden = false;
	private boolean editable = true;
	private boolean allowNull = false; // TODO This is not incorporated in all fiel types
	
	@Inject
	public AbstractField(WidgetFactory widgetFactory) {
		super();
		this.widgetFactory = widgetFactory;
	}

	public abstract T clean(String value, Map<String, String> errors) throws ValidationException;
	
	public T clean(String[] value, Map<String, String> errors) throws ValidationException {
		// TODO Should value == null,  value.length==0 be handled differently
		if (value == null || value.length == 0) {
			return clean("", errors);
		}
		return clean(value[0], errors);
	}
	
	public T[] cleanAll(String[] values, Map<String, String> errors) throws ValidationException {
		T[] clean = (T[]) new Object[values.length]; // ugly but it works...
		int i=0;
		for(String val : values) {
			clean[i++] = clean(val, errors);
		}
		return clean;
	}
	
	
	public Object cleanInitialValue(Object value) {
		if (value == null) {
			return getInitial();
		}
		return value;
	}

	public void handleAnnotation(Annotation annotation) {
		if (!(annotation instanceof FieldProperties)) return;
		FieldProperties props = (FieldProperties)annotation;
		this.setRequired(props.required());
		this.setEditable(props.editable());
		this.setAllowNull(props.allowNull());
		this.label = "".equals(props.label()) ? props.verboseName() : props.label();
		this.helpText = props.helpText();
		this.hidden = props.hidden();
		this.setWidgetAttrs(props.widgetAttrs());
		if (props.widgetClass() != FieldProperties.NoWidget.class) this.widgetClass = props.widgetClass();
	}
		
	public AbstractField<T> setName(String name) {
		this.name = name;
		return this;
	}
	
	public String getName() {
		return this.name;
	}

	public Widget getWidget() {
		if (this.widget == null) {
			if (this.widgetClass == null) {
				setWidget(widgetFactory.newWidget(TextInputWidget.class));
			} else {
				setWidget(widgetFactory.newWidget(widgetClass));
			}
		}
		Map<String, String> widgetAttrs = getWidgetAttrs();
		if (widgetAttrs != null) this.widget.getAttrs().putAll(getWidgetAttrs());
		
		return this.widget;
	}

	public Field<T> setWidget(Widget widget) {
		this.widget = widget;
		return this;
	}

	public boolean isRequired() {
		return required;
	}
	public AbstractField<T> setRequired(boolean required) {
		this.required = required;
		return this;
	}

	public Object getInitial() {
		return initial;
	}

	public AbstractField<T> setInitial(Object initial) {
		this.initial = initial;
		return this;
	}

	/**
	 * @deprecated Use getLabel instead
	 */
	public String getVerboseName() {
		return this.getLabel();
	}

	/**
	 * @deprecated Use setLabel instead
	 */
	public AbstractField<T> setVerboseName(String verboseName) {
		return this.setLabel(verboseName);
	}	
	
	public String getLabel() {
		return label;
	}

	public AbstractField<T> setLabel(String label) {
		this.label = label;
		return this;
	}	
	
	public Widget getHiddenWidget() {
		if (this.hiddenWidget == null) {
			this.hiddenWidget = new TextInputWidget().setHidden(true);
		}
		return this.hiddenWidget;		
	}

	public AbstractField<T> setHiddenWidget(Widget hiddenWidget) {
		this.hiddenWidget = hiddenWidget;
		return this;
	}
	
	public boolean isHidden() {
		return hidden;
	}

	public AbstractField<T> setHidden(boolean hidden) {
		this.hidden = hidden;
		return this;
	}

	public boolean isEditable() {
		return editable;
	}

	public Field<T> setEditable(boolean editable) {
		this.editable = editable;
		return this;
	}

	public boolean isAllowNull() {
		return allowNull;
	}

	public Field<T> setAllowNull(boolean allowNull) {
		this.allowNull = allowNull;
		return this;
	}

	public String getHelpText() {
		return helpText;
	}

	public Field<T> setHelpText(String helpText) {
		this.helpText = helpText;
		return this;
	}

	public Class<? extends Widget> getHiddenWidgetClass() {
		return hiddenWidgetClass;
	}

	public Class<? extends Widget> getWidgetClass() {
		return widgetClass;
	}

	public Field<T> setHiddenWidget(Class<? extends Widget> widgetClass) {
		this.hiddenWidgetClass = widgetClass;
		return this;
	}

	public Field<T> setWidget(Class<? extends Widget> widgetClass) {
		this.widgetClass = widgetClass;
		return this;
	}
	
	public Map<String, String> getWidgetAttrs() {
		return widgetAttrs;
	}
	
	public Field<T> setWidgetAttrs(String[] widgetAttrsStrings) {

		Map<String, String> widgetAttrs = getWidgetAttrs();
		if (widgetAttrs == null && widgetAttrsStrings.length > 0) {
			widgetAttrs = new LinkedHashMap<String, String>();
		}
		
		for(String s : widgetAttrsStrings) {
			String[] v = s.split(":");
			if (v != null && v.length == 2) {
				widgetAttrs.put(v[0], v[1]);
			} else if (v != null && v.length == 1) {
				widgetAttrs.put(v[0], null);
			}
		}
		
		setWidgetAttrs(widgetAttrs);
		
		return this;
	}
	
	public Field<T> setWidgetAttrs(Map<String, String> attrs) {
		this.widgetAttrs = attrs;
		return this;
	}
	
	public Field<T> setWidget(Class<? extends Widget> widgetClass, String... widgetAttrsStrings) {
		setWidget(widgetClass);
		if (widgetAttrs == null && widgetAttrsStrings.length > 0) {
			setWidgetAttrs(widgetAttrsStrings);
		}
		return this;
	}


}
